模板中的表達式雖然方便,但也只能用來做簡單的操作。如果在模板中書寫太多邏輯,會讓模板變得臃腫,難以維護。例如說,我們有這樣一個包含嵌套數組的對象:
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
我們想根據 author 是否已有一些書籍來展示不同的信息:
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
這裡的模板看起來有些複雜。我們必須認真看好一會兒才能明白它的計算依賴於 author.books。更重要的是,如果在模板中需要不止一次這樣的計算,我們可不想將這樣的代碼在模板裡重複好多遍。
因此我們推薦使用計算屬性來描述依賴響應式狀態的複雜邏輯。這是重構後的示例:
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一個計算屬性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
你可能注意到我們在表達式中像這樣調用一個函數也會獲得和計算屬性相同的結果:
<p>{{ calculateBooksMessage() }}</p>
// 組件中
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}
若我們將同樣的函數定義為一個方法而不是計算屬性,兩種方式在結果上確實是完全相同的,然而,不同之處在於計算屬性值會基於其響應式依賴被緩存。一個計算屬性僅會在其響應式依賴更新時才重新計算。這意味著只要 author.books 不改變,無論多少次訪問 publishedBooksMessage 都會立即返回先前的計算結果,而不用重複執行 getter 函數。
這也解釋了為什麼下面的計算屬性永遠不會更新,因為 Date.now() 並不是一個響應式依賴:
const now = computed(() => Date.now())
相比之下,方法調用總是會在重渲染髮生時再次執行函數。
為什麼需要緩存呢?想象一下我們有一個非常消耗性能的計算屬性 list,需要循環一個巨大的數組並做許多計算邏輯,並且可能也有其他計算屬性依賴於 list。沒有緩存的話,我們會重複執行非常多次 list 的 getter,然而這實際上沒有必要!如果你確定不需要緩存,那麼也可以使用方法調用。
計算屬性默認是只讀的。當你嘗試修改一個計算屬性時,你會收到一個運行時警告。只在某些特殊場景中你可能才需要用到“可寫”的屬性,你可以通過同時提供 getter 和 setter 來創建:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我們這裡使用的是解構賦值語法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
現在當你再運行 fullName.value = 'John Doe' 時,setter 會被調用而 firstName 和 lastName 會隨之更新。
Getter 不應有副作用
計算屬性的 getter 應只做計算而沒有任何其他的副作用,這一點非常重要,請務必牢記。舉例來說,不要改變其他狀態、在 getter 中做異步請求或者更改 DOM!一個計算屬性的聲明中描述的是如何根據其他值派生一個值。因此 getter 的職責應該僅為計算和返回該值。在之後的指引中我們會討論如何使用偵聽器根據其他響應式狀態的變更來創建副作用。
避免直接修改計算屬性值
從計算屬性返回的值是派生狀態。可以把它看作是一個“臨時快照”,每當源狀態發生變化時,就會創建一個新的快照。更改快照是沒有意義的,因此計算屬性的返回值應該被視為只讀的,並且永遠不應該被更改——應該更新它所依賴的源狀態以觸發新的計算。